#pragma once

#include "CoreMinimal.h"
#include "NeuronInterfaces.h"

class NEURONDATAREADERPLUGIN_API FSimpleNeuronDataDispatcher : public INeuronDataDispatchAbility
{
public:
	virtual FOnNeuronFrame& OnNeuronFrameArrived( ) override
	{
		return OnNeuronFrameEvent;
	}

	virtual void Dispatch( BvhDataHeader* Header, float* Data ) override
	{
		OnNeuronFrameEvent.Broadcast( Header, Data );
	}

	FSimpleNeuronDataDispatcher( ) {}
	virtual ~FSimpleNeuronDataDispatcher( ) {}

private:
	FOnNeuronFrame OnNeuronFrameEvent;
};

class NEURONDATAREADERPLUGIN_API FSimpleTcpConfig : public INeuronTcpConfigAbility
{
public:
	FSimpleTcpConfig( ) {}
	FSimpleTcpConfig( FIPv4Endpoint Endpoint )
	{
		SetRemoteEndpoint( Endpoint );
	}
	FSimpleTcpConfig( FString EndpointStr )
	{
		SetRemoteEndpointFromString( EndpointStr );
	}
	virtual ~FSimpleTcpConfig( ) {}

	//~ Begin INeuronTcpConfigAbility 
	////~ Begin INeuronNetConfigBaseAbility
	virtual bool Equals( TSharedPtr<INeuronNetConfigBaseAbility> Other ) override
	{
		if (Other.Get() == this)
		{
			return true;
		}

		if (Other->IsTcpConfig( ))
		{
			TSharedPtr<INeuronTcpConfigAbility> RemoteOther = StaticCastSharedPtr<INeuronTcpConfigAbility>( Other );
			if (RemoteOther)
			{
				return (RemoteOther->GetRemoteEndpoint( ) == RemoteEndpoint);
			}
		}

		return false;
	}

	virtual bool IsTcpConfig( ) const override
	{
		return true;
	}
	////~ End INeuronNetConfigBaseAbility

	virtual void SetRemoteEndpoint( FIPv4Endpoint Endpoint ) override
	{
		RemoteEndpoint = Endpoint;
	}

	virtual bool SetRemoteEndpointFromString( const FString& EndpointStr ) override
	{
		return FIPv4Endpoint::Parse( EndpointStr, RemoteEndpoint );
	}

	virtual FIPv4Endpoint GetRemoteEndpoint( ) override
	{
		return RemoteEndpoint;
	}
	//~ End INeuronTcpConfigAbility 

private:
	FIPv4Endpoint RemoteEndpoint;
};

class NEURONDATAREADERPLUGIN_API FSimpleUdpConfig : public INeuronUdpConfigAbility
{
public:
	FSimpleUdpConfig( ) {}
	FSimpleUdpConfig( uint16 Port )
	{
		SetLocalPort( Port );
	}
	virtual ~FSimpleUdpConfig( ) {}

	//~ Begin INeuronNetConfigBaseAbility
	virtual bool Equals( TSharedPtr<INeuronNetConfigBaseAbility> Other ) override
	{
		if (Other.Get( ) == this)
		{
			return true;
		}

		if (!Other->IsTcpConfig( ))
		{
			TSharedPtr<INeuronUdpConfigAbility> LocalOther = StaticCastSharedPtr<INeuronUdpConfigAbility>( Other );
			if (LocalOther)
			{
				return (LocalOther->GetPort( ) == InternalPort);
			}
		}

		return false;
	}
	
	virtual bool IsTcpConfig( ) const override
	{
		return false;
	}
	//~ End INeuronNetConfigBaseAbility

	//~ Begin INeuronUdpConfigAbility 
	virtual void SetLocalPort( uint16 Port ) override
	{
		InternalPort = Port;
	}

	virtual uint16 GetPort( ) override
	{
		return InternalPort;
	}
	//~ End INeuronUdpConfigAbility 

private:
	uint16 InternalPort;
};

class NEURONDATAREADERPLUGIN_API FSimpleNeuronDataSource : public INeuronDataSourceAbility
{
public:
	FSimpleNeuronDataSource( )
		: Dispatcher( MakeShared<FSimpleNeuronDataDispatcher>( ) )
		, SocketInternal( nullptr )
	{
	}
	virtual ~FSimpleNeuronDataSource( ) { Cutoff( ); }

	//~ Begin INeuronDataDispatcherAbility
	virtual FOnNeuronFrame& OnNeuronFrameArrived( ) override;
	virtual void Dispatch( BvhDataHeader* Header, float* Data ) override;
	//~ End INeuronDataDispatcherAbility

	//~ Begin INeuronConnectionAbility
	virtual void SetConfig( TSharedPtr<INeuronNetConfigBaseAbility> Config ) override;
	virtual TSharedPtr<INeuronNetConfigBaseAbility> GetConfig( ) override;
	virtual ConnectionStatus GetStatus( ) override;
	virtual SOCKET_REF& GetConnectionSocket( ) override;
	virtual bool Setup( ) override;
	virtual void Cutoff( ) override; // should only called in NeuronDataSource destructor.
	//~ End INeuronConnectionAbility

private:
	TSharedRef<INeuronDataDispatchAbility> Dispatcher;
	TSharedPtr<INeuronNetConfigBaseAbility> InternalConfig;
	SOCKET_REF SocketInternal;
};



class NEURONDATAREADERPLUGIN_API FNeuronDataReceiveAbility : public INeuronDataReceiveAbility
{
public:
	FNeuronDataReceiveAbility( ) {}
	virtual ~FNeuronDataReceiveAbility( ) {}

	//~ Begin INeuronInitAbility
	virtual void Init() override
	{
		
	}

	virtual void Uninit() override
	{
		
	}
	//~ End INeuronInitAbility

	//~ Begin INeuronDataReceiveAbility
	virtual FDelegateHandle AttachReceiveFunctionToDispatcher( TSharedPtr<INeuronDataDispatchAbility> NeuronDispatcher, FNeuronFrameDelegate Delegate ) override
	{
		if (NeuronDispatcher)
		{
			return NeuronDispatcher->OnNeuronFrameArrived( ).Add( Delegate );
		}
		return FDelegateHandle( );
	}

	virtual void DetachReceiveFunctionFromDispatcher( TSharedPtr<INeuronDataDispatchAbility> NeuronDispatcher, FDelegateHandle RecvFnHandle ) override
	{
		if (NeuronDispatcher)
		{
			NeuronDispatcher->OnNeuronFrameArrived( ).Remove( RecvFnHandle );
		}
	}
	//~ End INeuronDataReceiveAbility
};




////////////////////////////////////////////////////////////////////////
// The derived UObject must implement INeuronFrameCallback interface
////////////////////////////////////////////////////////////////////////
template<
	typename U,
	typename T = typename TEnableIf<TPointerIsConvertibleFromTo<U, UObject>::Value, TWeakObjectPtr<U>>::Type,
	typename = typename TEnableIf<TPointerIsConvertibleFromTo<U, INeuronFrameCallback>::Value>::Type
	>
class FSimpleUObjectNeuronDataReceiver
	: public FNeuronDataReceiveAbility 
	, public TSharedFromThis<FSimpleUObjectNeuronDataReceiver<U>>
{
public:
	FSimpleUObjectNeuronDataReceiver( U* ReceiverObject, TSharedPtr<INeuronDataSourceAbility> DataSource )
		: WeakObj( ReceiverObject )
		, Source( DataSource )
	{

	}
	virtual ~FSimpleUObjectNeuronDataReceiver( )
	{
		Uninit( );
	}

	void OnFrame( BvhDataHeader * Header, float* Data )
	{
		auto Obj = static_cast<INeuronFrameCallback*>(WeakObj.Get( ));
		if (Obj)
		{
			Obj->OnFrame( Header, Data );
		}
	}

	virtual void Init() override
	{
		RecvFnHandle = AttachReceiveFunctionToDispatcher( Source.Pin( ), FNeuronFrameDelegate::CreateSP( this, &FSimpleUObjectNeuronDataReceiver<U>::OnFrame ) );
	}

	virtual void Uninit() override
	{
		DetachReceiveFunctionFromDispatcher( Source.Pin( ), RecvFnHandle );
	}

private:
	T WeakObj;
	TWeakPtr<INeuronDataSourceAbility> Source;
	FDelegateHandle RecvFnHandle;
};


template<
	typename U,
	typename = typename TEnableIf<!TPointerIsConvertibleFromTo<U, UObject>::Value>::Type,
	typename = typename TEnableIf<TPointerIsConvertibleFromTo<U, INeuronFrameCallback>::Value>::Type
>
class FSimpleNeuronDataReceiver
	: public FNeuronDataReceiveAbility
	, public TSharedFromThis<FSimpleNeuronDataReceiver<U>>
{
public:
	FSimpleNeuronDataReceiver(U* Object, TSharedPtr<INeuronDataSourceAbility> DataSource )
		: Source( DataSource )
	{
		RecvFnHandle = AttachReceiveFunctionToDispatcher( DataSource, FNeuronFrameDelegate::CreateSP( Object, &U::OnFrame ) );
	}
	virtual ~FSimpleNeuronDataReceiver( )
	{
		DetachReceiveFunctionFromDispatcher( Source.Pin( ), RecvFnHandle );
	}

private:
	TWeakPtr<INeuronDataSourceAbility> Source;
	FDelegateHandle RecvFnHandle;
};
